/**
 * Copyright 2007 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.flest.controller
{
/**
 * @author		Ivan Petrov
 * @date		14/05/2007
 * @version		1.0
 * @purpose		Singleton controller.
*/
	import org.flest.command.ICommand;
	import org.flest.application.FlestApplication;
	import org.flest.constant.Errors;
	import org.flest.model.ModelLocator;
	import mx.states.State;
	import flash.events.Event;
	import mx.collections.ArrayCollection;
	import mx.resources.ResourceBundle;
	import org.flest.constant.FlestKey;
	import org.flest.util.ToolBox;
	import mx.core.UIComponent;
	import mx.core.Application;
	import mx.events.EffectEvent;
	import mx.core.IFlexDisplayObject;
	import mx.managers.PopUpManager;
	import flash.display.DisplayObject;
	import mx.managers.PopUpManagerChildList;
	
	[Bindable]
	public class FlestController
	{
		static private var instance:FlestController;
		
		[ArrayElementType(ICommand)]
		public var commands:Array;
		
		private var app:FlestApplication;
		private var popup:IFlexDisplayObject;
		
		/**
		 * Constructor.
		 */
		public function FlestController(singletonEnforcer:SingletonEnforcer)
		{
			super();
		}
		
		/**
		 * 
		 * @return 
		 * 
		 */		
		public static function getInstance():FlestController
		{
			if(FlestController.instance == null)
			{
				FlestController.instance = new FlestController(new SingletonEnforcer());
				FlestController.instance.commands =  new Array();
			}
			return FlestController.instance;
		}
		
		/**
		 * 
		 * @param name
		 * @param command
		 * 
		 */		
		public function addCommand(name:String, command:ICommand):void
		{
			if(commands[name.valueOf()] == null) 
			{
				commands[name.valueOf()] = command;
				//trace(commands);
			} else {
				var savedCommand:ICommand = commands[name.valueOf()];
				if(savedCommand != command) {
					throw Error(Errors.MSG_DUPLICATE_COMMAND_NAME);
				}
			}
		}
		
		/**
		 * 
		 * @param func
		 * @param args
		 * 
		 */		
		public function exec(func:Function, args:Array=null):void
		{
			try {
				func.apply(this, args);
			} catch(error:Error) {
				this.redirectToErrorPage(error);
			}
		}
		
		/**
		 * 
		 * @param func
		 * @param args
		 * @return 
		 * 
		 */		
		public function execReturn(func:Function, args:Array=null):Object
		{
			var out:Object = null;
			try {
				out = func.apply(this, args);
			} catch(error:Error) {
				this.redirectToErrorPage(error);
			}
			return out;
		}
		
		/**
		 * 
		 * @param component
		 * @param stateName
		 * 
		 */		
		public function showState(component:UIComponent, stateName:String):void
		{
			try {
				if(!this.isStateNameValid(component, stateName)) {
					this.redirectToErrorPage(new Error(Errors.MSG_INVALID_STATE_NAME));
					return;
				}
				component.currentState = stateName;
			} catch(error:Error) {
				this.redirectToErrorPage(error);
			}
		}
		
		/**
		 * 
		 * @param stateName
		 * @return 
		 * 
		 */		
		public function isStateNameValid(component:UIComponent, stateName:String):Boolean
		{
			if(stateName == null || stateName.length == 0) {
				//this is a base state
				return true;
			}
			if(ToolBox.isArrayEmpty(component.states)) {
				return false;
			}
			for(var i:int=0; i<component.states.length; i++) {
				var state:State = component.states[i];
				if(state.name == stateName) {
					return true;
				}
			}
			return false;
		}
		
		/**
		 * 
		 * @param error
		 * 
		 */		
		public function redirectToErrorPage(error:Error):void
		{
			var errorStateName:String = ModelLocator.getInstance().getValue(FlestKey.ERROR_STATE_NAME) as String;
			if(!ToolBox.isStringEmpty(errorStateName)) {
				var msg:String = error.name + "\n" + error.getStackTrace();
				ModelLocator.getInstance().addValue(FlestKey.GENERIC_ERROR_MSG, msg);
				this.app.currentState = errorStateName;
			} else {
				//if there was no error page defined, throw the error up the chain
				throw error;
			}
		}
		
		/**
		 * 
		 * @param parent
		 * @param className
		 * @param modal
		 * @param childList
		 * @return 
		 * 
		 * The method makes sure that there is only one popup at a time.
		 * It also centres popup.
		 * 
		 */		
		public function createPopup(parent:DisplayObject, className:Class, modal:Boolean = false, childList:String = null):IFlexDisplayObject
		{
			if(this.popup != null && this.popup.visible) {
				PopUpManager.removePopUp(this.popup);
			}
			this.popup = PopUpManager.createPopUp(parent, className, modal, childList);
			PopUpManager.centerPopUp(this.popup);
			return this.popup;
		}
		
		/**
		 * 
		 * @param popup
		 * 
		 * The method makes sure that there is only one popup at a time.
		 */		
		public function removePopup(popup:IFlexDisplayObject):void
		{
			if(this.popup === popup) {
				PopUpManager.removePopUp(this.popup);
			} else {
				PopUpManager.removePopUp(this.popup);
				PopUpManager.removePopUp(popup);
			}
		}
		
		public function removeAllPopups():void
		{
			if(this.popup != null) {
				PopUpManager.removePopUp(this.popup);
			}
			//TODO: find an existing popup (if any) and remove it too.
		}
		
		/**
		 * Disable the application for user interactivity
		 * while a command is running.
		 * 
		 */		
		public function disableUserInteractivity(param:*=null):void
		{
			Application.application.enabled = false;
		}
		
		/**
		 * Enable the application for user interactivity
		 * when a command is done.
		 * 
		 */		
		public function enableUserInteractivity(param:*=null):void
		{
			Application.application.enabled = true;
		}
		
		/**
		 * 
		 * @param className
		 * @return 
		 * 
		 */		
		public function isExistingCmd(className:String):Boolean
		{
			return this.commands[className] != null;
		}
		
		/**
		 * 
		 * @return 
		 * 
		 */		
		public function getApp():FlestApplication
		{
			return this.app;
		}
		
		/**
		 * 
		 * @param newApp
		 * 
		 */		
		public function setApp(newApp:FlestApplication):void
		{
			this.app = newApp;
		}
	}
}
class SingletonEnforcer {}